/*
 *
 *   Copyright (C) 2005 by Raymond Huang
 *   plushuang at users.sourceforge.net
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  ---
 *
 *  In addition, as a special exception, the copyright holders give
 *  permission to link the code of portions of this program with the
 *  OpenSSL library under certain conditions as described in each
 *  individual source file, and distribute linked combinations
 *  including the two.
 *  You must obey the GNU Lesser General Public License in all respects
 *  for all of the code used other than OpenSSL.  If you modify
 *  file(s) with this exception, you may extend this exception to your
 *  version of the file(s), but you are not obligated to do so.  If you
 *  do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source
 *  files in the program, then also delete it here.
 *
 */

#include <string.h>
#include <urlglib/url_info.h>
#include <urlglib/download_task.h>
#include <urlglib/ug_i18n.h>

// Global functions --------------------------------------------------
static GHashTable*  protocol_hash = NULL;

void download_task_add_protocol  (const char*  protocol,  DownloadTaskNewFunc fn)
{
	if (protocol_hash==NULL)
		protocol_hash = g_hash_table_new (g_str_hash, g_str_equal);

	g_hash_table_insert (protocol_hash, (gpointer)protocol, fn);
}

void download_task_add_protocols (const char** protocols, DownloadTaskNewFunc fn)
{
	while (*protocols) {
		download_task_add_protocol (*protocols, fn);
		protocols++;
	}
}

// DownloadTask private virtual functions ----------------------------
static void download_task_finalize (DownloadTask* dtask)
{
	if (dtask->finalize)
		dtask->finalize (dtask);
	else
        download_task_instance_finalize (dtask);
}

static int  download_task_run (DownloadTask* dtask)
{
	if (dtask->run)
		return dtask->run (dtask);

	return DOWNLOAD_TASK_ERROR;
}

// DownloadTask member functions -------------------------------------
#ifdef _WIN32
#include <windows.h>
void download_task_retry_delay (DownloadTask* dtask)
{
	int  count = dtask->download->retry_delay * 2;

	while (count-- && dtask->download->state != DOWNLOAD_STATE_STOP)
		Sleep (500);    // 0.5 seconds
}
#else
#include <unistd.h>
void download_task_retry_delay (DownloadTask* dtask)
{
	int  count = dtask->download->retry_delay * 2;

	while (count-- && dtask->download->state != DOWNLOAD_STATE_STOP)
		usleep (500000);    // 0.5 seconds
}
#endif

DownloadTask* download_task_new_from_protocol (const char* protocol, int protocol_len)
{
	DownloadTaskNewFunc  task_new;
	char*  protocol_str;

	if (protocol == NULL)
		return NULL;
	if (protocol_len == -1)
		protocol_len = strlen (protocol);

	protocol_str = g_strndup (protocol, protocol_len);
	task_new = g_hash_table_lookup (protocol_hash, protocol_str);
	g_free (protocol_str);

	return (task_new) ? task_new () : NULL;
}

void download_task_instance_init (DownloadTask* dtask)
{
	dtask->finalize        = NULL;
	dtask->run             = NULL;
	dtask->reference_count = 1;
	dtask->thread          = NULL;
	dtask->download        = NULL;
	dtask->proxy           = NULL;
	dtask->fullpath        = NULL;
	dtask->url             = NULL;
	dtask->referer         = NULL;
}

void download_task_instance_finalize (DownloadTask* dtask)
{
	g_message ("download_task finalize");
	download_task_set (dtask, NULL, NULL);

	if (dtask->proxy)
		proxy_node_unref (dtask->proxy);
	g_free (dtask->fullpath);
	g_free (dtask->url);
	g_free (dtask->referer);
}

void download_task_ref (DownloadTask* dtask)
{
	dtask->reference_count++;
}

void download_task_unref (DownloadTask* dtask)
{
	if (--dtask->reference_count == 0)
		download_task_finalize (dtask);
}

void download_task_set (DownloadTask* dtask, DownloadNode* dnode, gpointer data)
{
	if (dnode) {
		download_node_ref (dnode);
		dnode->thread = dtask;
	}

	if (dtask->download) {
		dtask->download->thread = NULL;
		download_node_unref (dtask->download);
	}

	dtask->download  = dnode;
	dtask->user_data = data;
}

char* download_task_load_fullpath (DownloadTask* dtask)
{
	DownloadNode* dnode = dtask->download;
	GString*  gstr;
	char* filename  = NULL;
	int   dir_len;
	int   n_retry;

	// free old filename
	g_free (dtask->fullpath);
	dtask->fullpath = NULL;

	// copy directory & filename
	download_node_lock (dnode);        // lock DownloadNode
	if (dnode->filename && *dnode->filename) {
		filename  = g_strdup (dnode->filename);
		gstr = g_string_sized_new (64);
		if (dnode->directory)
			g_string_append (gstr, dnode->directory);
	}
	download_node_unlock (dnode);      // unlock DownloadNode

	// check filename
	if (filename == NULL)
		return FALSE;

	// check & make directory
	if (gstr->len) {
		if (ug_make_dirs (gstr->str, -1) == FALSE) {
			g_string_free (gstr, TRUE);
			g_free (filename);
			return FALSE;
		}
		if (gstr->str[gstr->len-1] != G_DIR_SEPARATOR)
			g_string_append_c (gstr, G_DIR_SEPARATOR);
	}

	// if file already exist, try to rename.
	dir_len = gstr->len;
	g_string_append (gstr, filename);
	for (n_retry=0; n_retry < 100; n_retry++) {
		if (test_file_exist_utf8 (gstr->str) == FALSE) {
			download_node_lock (dnode);
			download_node_set_filename (dnode, gstr->str + dir_len);
			download_node_unlock (dnode);
			g_string_append (gstr, ".ug_");
			dtask->fullpath = g_strdup (gstr->str);
			break;
		}
		g_string_truncate (gstr, dir_len);
		g_string_append_printf (gstr, "%s.%u", filename, n_retry);
	}

	g_string_free (gstr, TRUE);
	g_free (filename);

	return dtask->fullpath;
}

int  download_task_refilename (DownloadTask* dtask)
{
	char* org_name;
	int   return_val;

	org_name = g_strndup (dtask->fullpath, strlen (dtask->fullpath) - 4);
	return_val = (ug_rename (dtask->fullpath, org_name) == 0);
	g_free (org_name);
	return return_val;
}

static char* encode_url_path (const char* url)
{
	GString* gstr;
	UrlInfo  uinfo;
	const unsigned char* cur;

    url_info_part (&uinfo, url);

	if (uinfo.path) {
        gstr = g_string_sized_new (32);
        g_string_append_len (gstr, url, uinfo.path - uinfo.protocol);
		for (cur=uinfo.path; *cur; cur++) {
			if (*cur == '\n')
				continue;
			else if (*cur > 0x7F || *cur == ' ')
				g_string_append_printf (gstr, "%c%X", '%', *cur);
			else
				g_string_append_c (gstr, *cur);
		}
		return g_string_free (gstr, FALSE);
	}
	return g_strdup (url);
}

char* download_task_load_url (DownloadTask* dtask, gboolean encoding)
{
	DownloadNode* dnode = dtask->download;
	UrlInfo  uinfo;
	int      referer_len;

	// free old url & referer
	g_free (dtask->url);
	dtask->url = NULL;
	g_free (dtask->referer);
	dtask->referer = NULL;

	download_node_lock (dnode);

	if (dnode->url) {
		dtask->url = (encoding) ? encode_url_path (dnode->url) : g_strdup (dnode->url);
		// handle referer
		if (dnode->referer) {
			if (encoding)
				dtask->referer = encode_url_path (dnode->referer);
			else
				dtask->referer = g_strdup (dnode->referer);
		} else {
			url_info_part (&uinfo, dtask->url);
			dtask->referer = g_strndup (uinfo.protocol, uinfo.location_len);
		}
	}

	download_node_unlock (dnode);

	return dtask->url;
}

ProxyNode* download_task_load_proxy (DownloadTask* dtask)
{
	DownloadNode* dnode = dtask->download;
	ProxyNode* proxy;

	download_node_lock (dnode);

	proxy = download_node_pick_proxy (dnode);
	if (proxy && proxy->host && *proxy->host) {
		if (dtask->proxy == NULL)
			dtask->proxy = proxy_node_new ();
		proxy_node_assign (dtask->proxy, proxy);
	} else if (dtask->proxy) {
		proxy_node_unref (dtask->proxy);
		dtask->proxy = NULL;
	}

	download_node_unlock (dnode);

	return dtask->proxy;
}

// download_task_activate () and thread function ---------------------

static gpointer thread_func (DownloadTask* dtask)
{
	DownloadNode* dnode = dtask->download;
	gint      task_code;
	guint     n_retry = 0;

	// check file and path
	if (download_task_load_fullpath (dtask) == NULL) {
		dnode->state = DOWNLOAD_STATE_ERROR;
		download_node_set_message (dnode, _("Filename repeated or directory can't be created."));
	}

	// download & retry loop
	while (dnode->state==DOWNLOAD_STATE_RETRY || dnode->state==DOWNLOAD_STATE_ACTIVE) {
		if (n_retry > dnode->retry_max) {
			dnode->state = DOWNLOAD_STATE_ERROR;
			download_node_set_message (dnode, _("Retry too many times."));
			break;
		}

		dnode->state = DOWNLOAD_STATE_ACTIVE;
		dnode->speed = 0;
		dnode->retry_times = n_retry;
		n_retry++;
		download_node_modified (dnode);

		// begin download here
		task_code = download_task_run (dtask);

		// check download task return code
		if (task_code==DOWNLOAD_TASK_ERROR || dnode->state==DOWNLOAD_STATE_DELETED) {
			ug_unlink (dtask->fullpath);
			dnode->state = DOWNLOAD_STATE_ERROR;
			break;
		}
		else if (task_code==DOWNLOAD_TASK_ABORT) {
			dnode->state = DOWNLOAD_STATE_STOP;
			break;
		}
		else if (task_code==DOWNLOAD_TASK_OK) {
			download_task_refilename (dtask);
			dnode->state = DOWNLOAD_STATE_COMPLETED;
			break;
		}
		else if (task_code==DOWNLOAD_TASK_ERROR_PROXY && dnode->proxy_mode!=DOWNLOAD_PROXY_TRY_ALL) {
			ug_unlink (dtask->fullpath);
			dnode->state = DOWNLOAD_STATE_ERROR;
			break;
		}

		dnode->state = DOWNLOAD_STATE_RETRY;
		download_node_modified (dnode);
		download_task_retry_delay (dtask);
	}

	// do some thing after download
	download_node_modified (dnode);
	dtask->thread = NULL;
	download_task_unref (dtask);

	return NULL;
}

void download_task_activate (DownloadTask* dtask)
{
	DownloadNode* dnode = dtask->download;

	download_task_ref (dtask);
	dnode->state = DOWNLOAD_STATE_ACTIVE;
	download_node_modified (dnode);
	dtask->thread = g_thread_create ((GThreadFunc)thread_func, dtask, TRUE, NULL);
}
